﻿using MvcFileUploader;
using MvcFileUploader.Models;
using SAIM.Models.DAL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Web;

namespace SAIM.Models.BLL
{
    public class AttachmentsBLL : IDisposable
    {
        private SAIMEntities db;
        private eVirtualEntities eVirtual;
        private List<AttachmentsDAL> _value;
        /// <summary>
        /// Dirección relativa en donde se guardan todos los archivos
        /// </summary>
        public static string PathForSaving
        {
            get
            {
                return "~/App_Data/SAIM_Uploads/";
            }
        }

        public AttachmentsBLL()
        {
            db = new SAIMEntities();
            eVirtual = new eVirtualEntities();
        }

        /// <summary>
        /// Contiene la información de todos los archivos adjuntos
        /// </summary>
        private List<AttachmentsDAL> Value
        {
            get { return (_value = _value ?? db.Attachments.ToList()); }
            set { _value = value; }
        }
       
        /// <summary>
        /// Agrega a la base de datos la información de un archivo adjuntado 
        /// </summary>
        /// <param name="fileName">Nombre del archivo</param>
        /// <param name="filePath">Localización del archivo</param>
        /// <param name="fileSize">Tamaño del archivo</param>
        /// <param name="userId">ID de usuario (Dueño del archivo)</param>
        /// <returns>Verdadero (true) si se agrego correctamente, de lo contrario Falso (false)</returns>
        public bool AttachFile(ref string fileName, string filePath, int fileSize, string userId)
        {
            bool isAttached;

            AttachmentsDAL attachment = new AttachmentsDAL()
            {
                Mask = Security.Hash(fileName),
                Name = fileName,
                FilePath = filePath,
                FileSize = fileSize,
                CreationDate = DateTime.Now,
                IdUser = userId
            };
            try
            {
                fileName = attachment.Mask;
                db.Attachments.Add(attachment);
                db.SaveChanges();
                isAttached = true;
            }
            catch (Exception)
            {
                isAttached = false;
            }

            return isAttached;
        }
        
        /// <summary>
        ///  Agrega un archivo adjuntado y lo asocia a un ticket
        /// </summary>
        /// <param name="fileName">Nombre del archivo</param>
        /// <param name="file">Archivo a adjuntar</param>
        /// <param name="userId">ID de usuario (Dueño del archivo)</param>
        /// <param name="ticketId">ID de ticket a asociar</param>
        /// <param name="filePath">Localización en donde se guardara el archivo</param>
        /// <returns>Variable indicadora del estatus de la operación</returns>
        public ViewDataUploadFileResult AttachFile(HttpPostedFileBase file, string userId, long ticketId, string filePath)
        {
            ViewDataUploadFileResult st = null;
            TicketsAttachmentsDAL ticketAttachment;
            string fileName;

            fileName = file.FileName;
            // Creación del nuevo archivo adjunto
            AttachmentsDAL attachment = new AttachmentsDAL()
            {
                Mask = Security.Hash(fileName),
                Name = fileName,
                FilePath = filePath,
                FileSize = file.ContentLength,
                CreationDate = DateTime.Now,
                IdUser = userId,
                HasEditions = false
            };
            try
            {
                // Se busca la existencia en el ticket del archivo a adjuntar 
                ticketAttachment = db.TicketsAttachments.FirstOrDefault(t => (t.Attachments.Name == attachment.Name) && (t.IdTicket == ticketId));
                // Si no existe el nombre del archivo se agrega a los archivos adjuntos del ticket
                if (ticketAttachment == null)
                {
                    #region Asignación del archivo adjunto al ticket
                    ticketAttachment = new TicketsAttachmentsDAL()
                    {
                        AttachedFile = attachment.Mask,
                        IdTicket = ticketId,
                    };
                    // Se agrega el archivo adjunto tanto a la tabla de archivos adjuntos como a los archivos adjuntos del ticket
                    db.Attachments.Add(attachment);
                    db.TicketsAttachments.Add(ticketAttachment);
                    #endregion
                }
                else // Si el nombre del archivo ya existe en la lista de archivos adjuntos para el ticket
                {
                    #region Asignación y generación del historial de la edición para el archivo adjunto
                    // Genera el registro que indica la edición del archivo adjunto
                    TicketAttachmentEditionsDAL attachmentEdition = new TicketAttachmentEditionsDAL()
                    {
                        EditionDate = DateTime.Now,
                        IdTicketAttachment = ticketAttachment.IdAttachment,
                        AttachedFile = ticketAttachment.AttachedFile,
                    };
                    // El archivo adjunto contiene ediciones
                    attachment.HasEditions = true;
                    // Nuevo valor para el archivo adjunto dado por el nombre encriptado
                    ticketAttachment.AttachedFile = attachment.Mask;
                    // Se agregan tanto el nuevo archivo adjunto como el registro de edición del mismo
                    db.Attachments.Add(attachment);
                    db.TicketAttachmentEditions.Add(attachmentEdition);
                    #endregion
                }

                #region Alamacenamiento del archivo en el servidor
                st = FileSaver.StoreFile(x =>
                {
                    x.File = file;
                    x.StorageDirectory = filePath ?? PathForSaving;
                    x.UrlPrefix = PathForSaving;// Url relativa del archivo

                    x.FileName = attachment.Mask;// Nombre de archivo encriptado
                    x.ThrowExceptions = true;
                });
                #endregion

                db.SaveChanges();
                fileName = attachment.Mask;
            }
            catch (Exception e)
            {
                st = new ViewDataUploadFileResult() { error = e.Message };
                // TODO: 
                // Si es necesario agregar código para deshacer cambios posibles 
                // tanto en el servidor como en la base de datos
            }

            return st; 
        }

        /// <summary>
        /// Devuelve todos los archivos adjuntos de un ticket determinado
        /// </summary>
        /// <param name="ticketId">ID de ticket</param>
        /// <returns>Lista con todos los archivos adjuntos</returns>
        public List<AttachmentsDAL> GetTicketAttachments(long ticketId)
        {
            return Value.FindAll(a => a.TicketsAttachments.FirstOrDefault(t => t.IdTicket == ticketId) != null);
        }

        public void GetAttachmentName(long ticketId, string Mask, ref string filePath, ref string fileName)
        {
            AttachmentsDAL attachment = Value.FirstOrDefault(a =>
                (a.Mask == Mask) &&
                (
                    (a.TicketsAttachments.FirstOrDefault(t => t.IdTicket == ticketId) != null) ||
                    (a.TicketAttachmentEditions.FirstOrDefault(t => t.TicketsAttachments.IdTicket == ticketId) != null)
                )
            );

            if (attachment != null)
            {
                filePath = attachment.FilePath;
                fileName = attachment.Name;
            }
            else
            {
                filePath = fileName = string.Empty;
            }
        }

        public void GetAttachmentName(string listId, string Mask, ref string filePath, ref string fileName)
        {
            eVirtualAttachmentsDAL attachment = eVirtual.Attachments.FirstOrDefault(a =>
                (a.Mask == Mask) &&
                (
                    (a.ListsAttachments.FirstOrDefault(b => b.ListId == listId) != null) ||
                    (a.ListAttachmentEditions.FirstOrDefault(b => b.Lists.Id == listId) != null)
                )
            );

            if (attachment != null)
            {
                filePath = attachment.FilePath;
                fileName = attachment.Name;
            }
            else
            {
                filePath = fileName = string.Empty;
            }
        }

        public List<TicketAttachmentEditionsDAL> GetAttachmentEditions(long idTicket, string fileName)
        {
            List <TicketAttachmentEditionsDAL> attachments = db.TicketAttachmentEditions.ToList().FindAll(a => 
                a.TicketsAttachments.IdTicket == idTicket && 
                a.TicketsAttachments.Attachments.Name == fileName);
            attachments.Reverse();

            return attachments;

        }

        public List<ListAttachmentEditionsDAL> GetAttachmentEditions(string listId, long columnId, string fileName)
        {
            List<ListAttachmentEditionsDAL> attachments = eVirtual.ListAttachmentEditions.ToList().FindAll(a =>
                a.Lists.Id == listId &&
                a.ColumnId == columnId &&
                a.Attachments.Name == fileName);
            attachments.Reverse();

            return attachments;

        }

        public ViewDataUploadFileResult AttachListFile(HttpPostedFileBase file, string userId, string listId, long columnId, string filePath)
        {
            ViewDataUploadFileResult st = null;
            string fileName;

            try
            {
                fileName = file.FileName;
                if (fileName == string.Empty)
                    throw new Exception("File name required");
                // Creación del nuevo archivo adjunto
                eVirtualAttachmentsDAL attachment = new eVirtualAttachmentsDAL()
                {
                    Mask = Security.Hash(fileName),
                    Name = fileName,
                    FilePath = filePath,
                    FileSize = file.ContentLength,
                    CreationDate = DateTime.Now,
                    UserId = userId,
                    HasEditions = false
                };
                ListsAttachmentsDAL listAttachment;
                ListsDAL list;
                // Busqueda de la lista
                list = eVirtual.Lists.FirstOrDefault(a => a.Id == listId && a.UsersLists.FirstOrDefault(b => (b.UserId == userId) && (b.DMLPermissions == true)) != null);
                // Se busca la existencia del archivo a adjuntar en la lista
                try
                {
                    listAttachment = list.ListsAttachments.FirstOrDefault(a => a.Attachments.Name == attachment.Name && a.ColumnId == columnId);
                }
                catch(Exception)
                {
                    listAttachment = null;
                }
                // Si no existe el nombre del archivo se agrega a los archivos adjuntos de la lista
                if (listAttachment == null)
                {
                    #region Asignación del archivo adjunto a la lista
                    listAttachment = new ListsAttachmentsDAL()
                    {
                        AttachedFile = attachment.Mask,
                        ListId = listId,
                        ColumnId = columnId
                    };
                    eVirtual.Attachments.Add(attachment);
                    eVirtual.ListsAttachments.Add(listAttachment);
                    #endregion
                }
                else // Si el nombre del archivo ya existe en los archivos adjuntos de la lista
                {
                    #region Asignación y generación del historial de la edición para el archivo adjunto
                    // Genera el registro que indica la edición del archivo adjunto
                    ListAttachmentEditionsDAL attachmentEdition = new ListAttachmentEditionsDAL()
                    {
                        ColumnId = columnId,
                        EditionDate = DateTime.Now,
                        ListId = list.Id,
                        AttachedFile = listAttachment.AttachedFile,
                    };
                    // El archivo adjunto contiene ediciones
                    attachment.HasEditions = true;

                    eVirtual.ListsAttachments.Remove(listAttachment);
                    listAttachment = new ListsAttachmentsDAL()
                    {
                        AttachedFile = attachment.Mask,
                        ListId = listId,
                        ColumnId = columnId
                    };
                    eVirtual.Attachments.Add(attachment);
                    eVirtual.ListsAttachments.Add(listAttachment);
                    eVirtual.ListAttachmentEditions.Add(attachmentEdition);
                    #endregion
                }

                #region Alamacenamiento del archivo en el servidor
                st = FileSaver.StoreFile(x =>
                {
                    x.File = file;
                    x.StorageDirectory = filePath ?? PathForSaving;
                    x.UrlPrefix = PathForSaving;// Url relativa del archivo

                    x.FileName = attachment.Mask;// Nombre de archivo encriptado
                    x.ThrowExceptions = false;
                });
                #endregion

                eVirtual.SaveChanges();
                fileName = attachment.Mask;
            }
            catch (Exception e)
            {
                st = new ViewDataUploadFileResult() 
                { 
                    name = file.FileName,
                    error = e.Message 
                };
                // TODO: 
                // Si es necesario agregar código para deshacer cambios posibles 
                // tanto en el servidor como en la base de datos
            }

            return st;
        }

        public GetListAttachmentsViewModel GetListAttachements(string listId, long columnId, string userId, int current, int rowCount, string searchPhrase)
        {
            ListsDAL list = (from e in eVirtual.Lists
                             where
                                (e.Id == listId) &&
                                (e.UsersLists.FirstOrDefault(b => b.UserId == userId && b.DMLPermissions == true) != null)
                             select e).FirstOrDefault();
            if (list != null)
            {
                GetListAttachmentsViewModel model;
                List<eVirtualAttachmentsDAL> attachments = (from e in list.ListsAttachments 
                                                            where e.ColumnId == columnId 
                                                            orderby e.Attachments.CreationDate descending 
                                                            select e.Attachments).ToList();
                model = new GetListAttachmentsViewModel(current, rowCount);
                model.total = attachments.Count;
                foreach (eVirtualAttachmentsDAL attachment in attachments)
                {
                    double fileSize = attachment.FileSize;
                    model.rows.Add(new ListAttachment()
                    {
                        Mask = attachment.Mask,
                        FileName = attachment.Name,
                        FileSize = fileSize < 1000000 ? (fileSize / 1000).ToString("N2").Replace(',', '.') + " KB" : (fileSize / 1000000).ToString("N2").Replace(',', '.') + " MB",
                        CreationDate = attachment.CreationDate.ToString(),
                        UserName = AspNetUsersBLL.FindUser(attachment.UserId, string.Empty).Find(a => a.Id == attachment.UserId).UserName,
                        HasEditions = attachment.HasEditions ?? false
                    });
                }
                return model;
            }
            else
            {
                throw new System.Web.HttpException(404, "Element not found");
            }
        }

        public bool RemoveAttachment(eVirtualAttachmentsDAL attachment)
        {
            bool status;
            string fullPath;

            try
            {
                fullPath = System.IO.Path.Combine(attachment.FilePath, attachment.Mask);
                if (System.IO.File.Exists(fullPath))
                {
                    System.IO.File.Delete(fullPath);
                    status = true;
                }
                else
                {
                    status = false;
                }
            }
            catch(Exception)
            {
                status = false;
            }
            return status;
        }

        public bool RemoveFile(string listId, long columnId, string mask)
        {
            bool status = true;

            try
            {
                // Eliminamos las ediciones registradas para el archivo
                IEnumerable<ListAttachmentEditionsDAL> attachmentEditions = (from e in eVirtual.ListAttachmentEditions
                                                                             where (e.ColumnId == columnId) && (e.ListId == listId)
                                                                             select e);
                foreach (ListAttachmentEditionsDAL edition in attachmentEditions)
                {
                    if (RemoveAttachment(edition.Attachments))
                    {
                        string attachmentMask = edition.Attachments.Mask;
                        eVirtual.ListAttachmentEditions.Remove(edition);
                        eVirtual.Attachments.Remove(eVirtual.Attachments.FirstOrDefault(a => a.Mask == attachmentMask));
                    }
                    else
                    {
                        status = false;
                        break;
                    }
                }
                if (status) // Si se eliminaron todas las ediciones del archivo continuamos con la eliminacion de las relaciones entre la lista y el archivo
                {
                    IEnumerable<ListsAttachmentsDAL> listAttachments = (from e in eVirtual.ListsAttachments
                                                                        where (e.ColumnId == columnId) && (e.ListId == listId) && (e.AttachedFile == mask)
                                                                        select e);
                    foreach (ListsAttachmentsDAL listAttachment in listAttachments)
                    {
                        if (RemoveAttachment(listAttachment.Attachments))
                            eVirtual.ListsAttachments.Remove(listAttachment);
                        else
                        {
                            status = false;
                            break;
                        }
                    }
                    if(status)  // Si las relaciones antre el archivo y la lista, junto con sus edciones fueron elimadas. Eliminamos el archivo
                        eVirtual.Attachments.RemoveRange((from e in eVirtual.Attachments
                                                          where (e.Mask == mask)
                                                          select e));
                }
                eVirtual.SaveChanges();
            }
            catch (Exception) 
            {
                status = false;
            }
            return status;
        }

        public void Dispose()
        {
            db.Dispose();
        }
    }
}